//
//  ViewController.m
//  RCA的简单使用
//
//  Created by wsj on 16/8/13.
//  Copyright © 2016年 wsj. All rights reserved.
//
#warning 本工程为介绍RAC里面的类的运用，基本上都是将博客的代码自己敲写一下加上自己的理解（重点），也为基础篇。
#warning 本工程为七秒记忆鱼儿著，转载请备注七秒记忆鱼儿。用于自己也是刚刚了解RAC与MVVM框架写的不好请见谅，我也是根据别人的博客写的，博客地址为
// http://www.jianshu.com/p/87ef6720a096 基础篇
// http://www.jianshu.com/p/e10e5ca413b7 进阶篇
#warning  我是七秒记忆鱼儿，如果在使用中出现的bug的话请联系593216709，期待你的指导，转载时请备注七秒记忆鱼儿
#warning  本人简书地址为http://www.jianshu.com/users/e3402afea1f1/latest_articles
#warning  我们的swfit简书专题为http://www.jianshu.com/collection/e30d753afdee
#warning   iOS开发者交流群:446310206 小白进阶群！！期待你的加入！demo在这里！
#warning   wfit交流群为512847147 小白进阶群！！期待你的加入！demo在这里！
#warning 参考资料
// http://www.jianshu.com/p/87ef6720a096
#import "ViewController.h"
#import "ReactiveCocoa.h"
#import "FlagItem.h"
#import "TwoViewController.h"
#import "RedView.h"
@interface ViewController ()
/**事件处理的类*/
@property(nonatomic,strong)RACCommand *command;
@property (weak, nonatomic) IBOutlet UIButton *button;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
  
    // 4.代替通知
    // 把监听到的通知转换信号
    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
        NSLog(@"键盘弹出");
    }];
    
    // 5.监听文本框的文字改变
//    [_textField.rac_textSignal subscribeNext:^(id x) {
    
//        NSLog(@"文字改变了%@",x);
//    }];
}
#pragma mark <RACSignal的使用>
/**
 *  RACSignal的使用
 */
-(void)test_one
{
    // RACSignal使用步骤：
    // 1.创建信号 + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
    // 2.订阅信号,才会激活信号. - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
    // 3.发送信号 - (void)sendNext:(id)value
    
    
    // RACSignal底层实现：
    // 1.创建信号，首先把didSubscribe保存到信号中，还不会触发。
    // 2.当信号被订阅，也就是调用signal的subscribeNext:nextBlock
    // 2.2 subscribeNext内部会创建订阅者subscriber，并且把nextBlock保存到subscriber中。
    // 2.1 subscribeNext内部会调用siganl的didSubscribe
    // 3.siganl的didSubscribe中调用[subscriber sendNext:@1];
    // 3.1 sendNext底层其实就是执行subscriber的nextBlock
    
    
    /**创建信号*/
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        // 当有订阅者订阅信号的时候就会出调用block
        // /订阅信号,才会激活信号. - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
        NSLog(@"11");
        // 2.0 发送信号，也就是发送的值
        [subscriber sendNext:@3];
        
        //3.0 信号发送完毕
        // 如果发送信号完毕后，最好发送发送信号完成方法，内部会动调用[RACDisposable disposble]取消订阅信号
        [subscriber sendCompleted];
        
        return [RACDisposable disposableWithBlock:^{
            // block 调用时刻，当发送完成或者发送错的时候，都会自动执行block，取消订阅信号。
            // 执行玩block后，当前信号就不在被订阅、  没有理解
            NSLog(@"信号销毁");
        }];
    }];
    /**订阅信号，才可以激活信号*/
    [signal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    /**订阅信号，才可以激活信号*/
    [signal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
}
#pragma mark <RACSubject和RACReplaySubject的用法和区别>
/**
 * RACSubject和RACReplaySubject的用法和区别
 */
-(void)test_two
{
#warning 文章作者的理解
    /**  文章作者的理解
     *  // RACSubject使用步骤
     // 1.创建信号 [RACSubject subject]，跟RACSiganl不一样，创建信号时没有block。
     // 2.订阅信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
     // 3.发送信号 sendNext:(id)value
     
     // RACSubject:底层实现和RACSignal不一样。
     // 1.调用subscribeNext订阅信号，只是把订阅者保存起来，并且订阅者的nextBlock已经赋值了。
     // 2.调用sendNext发送信号，遍历刚刚保存的所有订阅者，一个一个调用订阅者的nextBlock。
     */
#warning  个人理解
    // RACSubject 的使用用法
    // 不可以先发送信号，再去订阅的
    
    //底册实现原理：将订阅者保存起来，等到信号发送时候，将保存的订阅者遍历出来进行赋值
    
    RACSubject *subject = [RACSubject subject];
    
    [subject subscribeNext:^(id x) {
        NSLog(@"第一个订阅者%@",x);
    }];
    [subject subscribeNext:^(id x) {
        NSLog(@"第二个订阅者%@",x);
    }];
    // 发送信号
    [subject sendNext:@17];
    
    
#warning 文章作者的理解
    /**
     *   // RACReplaySubject使用步骤:
     // 1.创建信号 [RACSubject subject]，跟RACSiganl不一样，创建信号时没有block。
     // 2.可以先订阅信号，也可以先发送信号。
     // 2.1 订阅信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
     // 2.2 发送信号 sendNext:(id)value
     
     // RACReplaySubject:底层实现和RACSubject不一样。
     // 1.调用sendNext发送信号，把值保存起来，然后遍历刚刚保存的所有订阅者，一个一个调用订阅者的nextBlock。
     // 2.调用subscribeNext订阅信号，遍历保存的所有值，一个一个调用订阅者的nextBlock
     
     // 如果想当一个信号被订阅，就重复播放之前所有值，需要先发送信号，在订阅信号。
     // 也就是先保存值，在订阅值。
     */
#warning  个人理解
    // RACReplaySubject 的底层实现远离
    // 1.0 创建信号后  可以先订阅信号，再去发送信号，也是可以先发送信号再去订阅信号的
    // 底层实现原理 1.0等发送信号的时候，将发送信号的值保存起来，等遇到订阅信号者，再去遍历发送信号的值给订阅者。注意：发送信号在订阅信号之前
    // 2.0 当订阅者在发送信号之前的时候，要先将订阅者保存起来，当发送信号的时候，在去遍历保存的订阅者，将信号的值复制给订阅者  和RACSubject远离一样
    
    // 创建信号
    RACReplaySubject *replaySubject = [RACReplaySubject subject];
    // 发送信号
    
    [replaySubject sendNext:@89];
    [replaySubject sendNext:@99];
    // 订阅信号
    [replaySubject subscribeNext:^(id x) {
        NSLog(@"第一个订阅者获得的值%@",x);
    }];
    [replaySubject subscribeNext:^(id x) {
        NSLog(@"第二个订阅者得到的值%@",x);
    }];

}
#pragma mark <数组和字典以及字段传模型的使用>
/**
 *  数组和字典以及字段传模型的使用
 */
-(void)test_three
{
#warning 遍历数组
    //  RACSequence:RAC中的集合类，用于代替NSArray,NSDictionary,可以使用它来快速遍历数组和字典。
    // 1.0 将数组转化为集合RACSequence array.rac_sequence
    // 2.0 将集合RACSequence转换为RACSigin信号类array.rac_sequence.signal
    // 3.0 订阅信号，激活信号，会自动把集合里的所有值边遍历出来
    NSArray *array = @[@8,@90,@76];
    [array.rac_sequence.signal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
#warning  遍历字典
    NSDictionary *dict = @{@"name":@"wsj",@"age":@"25"};
    [dict.rac_sequence.signal subscribeNext:^(id x) {
        // 解包元祖，会将元组里面的值，按照顺序给参数里面的变量复制
        // 也就是会边里字典，按照一一对应的法则进行复制，key就是字典里面的key，value就相当于值，如果写反了，也就是复制反了。
        RACTupleUnpack(NSString *key,NSString *value) = x;
        // 相当于一下写法
        //        NSString *key = x[0];
        //        NSString *value = x[1];
        NSLog(@"%@ %@",key,value);
    }];
    
#warning 字段传模型
    
    NSArray *dict_array = @[@{@"name":@"wsj",@"age":@"25"}, @{@"name":@"wsj2",@"age":@"24"}, @{@"name":@"wsj3",@"age":@"26"}, @{@"name":@"wsj4",@"age":@"27"}];
    NSMutableArray *array_M = [NSMutableArray array];
    // 遍历数组 x就是字典
    [dict_array.rac_sequence.signal subscribeNext:^(id x) {
        // [FlagItem flagWithDict:x]字段转模型的方法在这没有实现
        
        [array_M addObject:[FlagItem flagWithDict:x]];
        //        NSLog(@"%@",array_M);
    }];
    
    
    // 高级写法
    // 1.0 map:映射的意思，目的：把原始值映射为新值。
    // 2.0 array 将集合转化为数组
    // 3.0 底层实现：当信号被订阅，会遍历集合里面的值，映射为新值，并且保存到新的数组中
    NSArray *model_array = [[dict_array.rac_sequence map:^id(id value) {
        
        return [FlagItem flagWithDict:value];
    }] array];
    NSLog(@"%@",model_array);
}
#pragma mark <RACCommand>
/**
 *  RACCommand：RAC中用于处理事件的类，可以把事件如何处理,事件中的数据如何传递，包装到这个类中，他可以很方便的监控事件的执行过程。
     使用场景:监听按钮点击，网络请求
 */
-(void)test_four
{
#warning 别人的注释
    /**
     // 一、RACCommand使用步骤:
     // 1.创建命令 initWithSignalBlock:(RACSignal * (^)(id input))signalBlock
     // 2.在signalBlock中，创建RACSignal，并且作为signalBlock的返回值
     // 3.执行命令 - (RACSignal *)execute:(id)input
     
     // 二、RACCommand使用注意:
     // 1.signalBlock必须要返回一个信号，不能传nil.
     // 2.如果不想要传递信号，直接创建空的信号[RACSignal empty];
     // 3.RACCommand中信号如果数据传递完，必须调用[subscriber sendCompleted]，这时命令才会执行完毕，否则永远处于执行中。
     // 4.RACCommand需要被强引用，否则接收不到RACCommand中的信号，因此RACCommand中的信号是延迟发送的。
     
     // 三、RACCommand设计思想：内部signalBlock为什么要返回一个信号，这个信号有什么用。
     // 1.在RAC开发中，通常会把网络请求封装到RACCommand，直接执行某个RACCommand就能发送请求。
     // 2.当RACCommand内部请求到数据的时候，需要把请求的数据传递给外界，这时候就需要通过signalBlock返回的信号传递了。
     
     // 四、如何拿到RACCommand中返回信号发出的数据。
     // 1.RACCommand有个执行信号源executionSignals，这个是signal of signals(信号的信号),意思是信号发出的数据是信号，不是普通的类型。
     // 2.订阅executionSignals就能拿到RACCommand中返回的信号，然后订阅signalBlock返回的信号，就能获取发出的值。
     
     // 五、监听当前命令是否正在执行executing
     
     // 六、使用场景,监听按钮点击，网络请求
     */
    
    
    /**创建个事件处理对象*/
    RACCommand *command = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {
        // 在这里执行命令
        // 这个是需要返回一个信号的。不能返回nil，如果不想放回信号，可以返回一个空的信号
        //        [RACSignal empty];
        return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [subscriber sendNext:@"发送数据"];
            // 注意一定要发送结束发送信号
            [subscriber sendCompleted];
            return nil;
        }];
    }];
    self.command = command;// 因为信号可以是延迟发送的，必须强引用下，保证不会被销毁
    // 3.订阅RACCommand中的信号
    [self.command.executionSignals subscribeNext:^(id x) {
        [x subscribeNext:^(id x) {
            NSLog(@"收到你发送的数据了%@",x);
        }];
        
    }];
     // 高级用法
    // RAC高级用法
    // switchToLatest:用于signal of signals，获取signal of signals发出的最新信号,也就是可以直接拿到RACCommand中的信号
    [command.executionSignals.switchToLatest subscribeNext:^(id x) {
        
        NSLog(@"%@",x);
    }];
    
    // 4.监听命令是否执行完毕,默认会来一次，可以直接跳过，skip表示跳过第一次信号。
    [[command.executing skip:1] subscribeNext:^(id x) {
        
        if ([x boolValue] == YES) {
            // 正在执行
            NSLog(@"正在执行");
            
        }else{
            // 执行完成
            NSLog(@"执行完成");
        }
        
    }];
    // 5.执行命令
    [self.command execute:@1];
}
/**
 * RACMulticastConnection使用步骤:用于当一个信号，被多次订阅时，为了保证创建信号时，避免多次调用创建信号中的block，造成副作用，可以使用这个类处理。
 */
-(void)test_five
{
#warning 别人的注释
    // RACMulticastConnection使用步骤:
    // 1.创建信号 + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
    // 2.创建连接 RACMulticastConnection *connect = [signal publish];
    // 3.订阅信号,注意：订阅的不在是之前的信号，而是连接的信号。 [connect.signal subscribeNext:nextBlock]
    // 4.连接 [connect connect]
    
    // RACMulticastConnection底层原理:
    // 1.创建connect，connect.sourceSignal -> RACSignal(原始信号)  connect.signal -> RACSubject
    // 2.订阅connect.signal，会调用RACSubject的subscribeNext，创建订阅者，而且把订阅者保存起来，不会执行block。
    // 3.[connect connect]内部会订阅RACSignal(原始信号)，并且订阅者是RACSubject
    // 3.1.订阅原始信号，就会调用原始信号中的didSubscribe
    // 3.2 didSubscribe，拿到订阅者调用sendNext，其实是调用RACSubject的sendNext
    // 4.RACSubject的sendNext,会遍历RACSubject所有订阅者发送信号。
    // 4.1 因为刚刚第二步，都是在订阅RACSubject，因此会拿到第二步所有的订阅者，调用他们的nextBlock
    
    
    // 需求：假设在一个信号中发送请求，每次订阅一次都会发送请求，这样就会导致多次请求。
    // 解决：使用RACMulticastConnection就能解决.
    
    // 创建信号
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSLog(@"发送信号");
        [subscriber sendNext:@89];
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"信号销毁");
        }] ;
    }];
    // 创建连接  RACMulticastConnection可以解决同一个信号，被多次订阅后的重复创建
    RACMulticastConnection *connection = [signal publish];
    // 订阅信号
    // 如果只是这样订阅信号 每次订阅都会常见新的信号和发送信号，解决重复发送和创建就使用订阅connection.signal
    //    [signal subscribeNext:^(id x) { }];
    
    
    [connection.signal subscribeNext:^(id x) {
        NSLog(@"第一个订阅者");
    }];
    [connection.signal subscribeNext:^(id x) {
        NSLog(@"第二个订阅者");
    }];
    // 连接信号
    [connection connect];
}
#pragma mark <演示监听button的点击事件>
-(void)test_six
{
    /**
     *  订阅按钮的点信号
     *
     *  @param x 按钮本身
     *
     *  @return
     */
    [[self.button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
        NSLog(@"%@",x);
        TwoViewController *twoVC = [[TwoViewController alloc]init];
        twoVC.subject = [RACSubject subject];
        
        [twoVC.subject subscribeNext:^(id x) {
            self.view.backgroundColor = x;
        }];
        [self presentViewController:twoVC animated:YES completion:nil];
    }];
}
#pragma mark <RAC中代理的替代使用>
/**
 *  RAC中代理的替代使用
 */
-(void)test_seven
{
    RedView *red = [[RedView alloc]initWithFrame:CGRectMake(100, 100, 200, 200)];
    [[red rac_signalForSelector:@selector(buttonClick:)]subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    [self.view addSubview:red];
}
#pragma mark <处理多个信号都完成之后调用的方法>
-(void)test_eight
{
    // 6.处理多个请求，都返回结果的时候，统一做处理.
    RACSignal *request1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
        // 发送请求1
        [subscriber sendNext:@"发送请求1"];
        return nil;
    }];
    
    RACSignal *request2 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        // 发送请求2
        [subscriber sendNext:@"发送请求2"];
        return nil;
    }];
    
    // 使用注意：几个信号，参数一的方法就几个参数，每个参数对应信号发出的数据。
    [self rac_liftSelector:@selector(updateUIWithR1:r2:) withSignalsFromArray:@[request1,request2]];
}
// 更新UI
- (void)updateUIWithR1:(id)data r2:(id)data1
{
    NSLog(@"更新UI%@  %@",data,data1);
}
#pragma mark <常用的宏>
-(void)test_nine
{
    // 只要文本框文字改变，就会修改label的文字
//    RAC(self.labelView,text) = _textField.rac_textSignal;
    
    // RACObserve(self, name):监听某个对象的某个属性,返回的是信号。
    [RACObserve(self.view, center) subscribeNext:^(id x) {
        
        NSLog(@"%@",x);
    }];
    
    //// 把参数中的数据包装成元组
    // RACTuple *tuple = RACTuplePack(@10,@20);
    
    
    
    // 把参数中的数据包装成元组
    RACTuple *tuple = RACTuplePack(@"xmg",@20);
    
    // 解包元组，会把元组的值，按顺序给参数里面的变量赋值
    // name = @"xmg" age = @20
//    RACTupleUnpack(NSString *name,NSNumber *age) = tuple;
}
@end
